﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Text.RegularExpressions;
using System.Windows.Forms;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;

using DevScope.CartaoDeCidadao;

namespace DemoCartaoCidadao
{
  public partial class frmMain : Form
  {
    private bool isCardPresent;
    private string currentReader;
    private List<string> readerList;
    private List<string> knownCards = new List<string>(new string[] { "3B 7D 95 00 00 80 31 80 65 B0 83 11 C0 A9 83 00 90 00", "3B 95 95 40 FF D0 00 54 01 31", "3B 95 95 40 FF D0 00 54 01 32" });

    private const string REGEX_ISSUER_NAME = "CN=([^,]+),|CN=([^,]+)$";
    private const string REGEX_SUBJECT_NAME = "^CN=([^,]+),";

    private ProxyInfo proxyInfo;
    private byte[] notesFilePath = { 0x3F, 0x00, 0x5F, 0x00, (byte)0xEF, 0x07 };

    private SCWatcher scWatcher;

    private delegate void OnSetStatusText(string text);
    private delegate void OnShowInfoMessage(string caption, string text);
    private delegate void OnShowErrorMessage(string caption, string text);
    private delegate void OnLoadData();

    private void SetStatusText(string text)
    {
      if (statusStrip1.InvokeRequired)
      {
        Invoke(new OnSetStatusText(SetStatusText), new object[] { text });
      }
      else
      {
        tslblStatus.Text = text;
        statusStrip1.Update();
      }
    }

    /// <summary>
    /// 
    /// </summary>
    /// <param name="caption"></param>
    /// <param name="text"></param>
    private void ShowInfoMessage(string caption, string text)
    {
      if (this.InvokeRequired)
      {
        Invoke(new OnShowInfoMessage(ShowInfoMessage), new object[] { caption, text });
      }
      else
      {
        MessageBox.Show(this, text, caption, MessageBoxButtons.OK, MessageBoxIcon.Information);
      }
    }

    private void ShowErrorMessage(string caption, string text)
    {
      if (this.InvokeRequired)
      {
        Invoke(new OnShowErrorMessage(ShowErrorMessage), new object[] { caption, text });
      }
      else
      {
        MessageBox.Show(this, text, caption, MessageBoxButtons.OK, MessageBoxIcon.Error);
      }
    }

    private void LoadData()
    {
      if (tabControl1.InvokeRequired)
      {
        Invoke(new OnLoadData(LoadData));//, , new object[] { selectedPanel });
      }
      else
      {
        if (!isCardPresent)
        {
          ShowInfoMessage("Cartão de Cidadão", "Não foi detectado nenhum cartão de cidadão.");
          return;
        }
        SetStatusText("A ler dados do cartão...");
        try
        {
          Cursor = Cursors.WaitCursor;
          switch (tabControl1.SelectedIndex)
          {
            case 0:
            case 1:
              LoadIdentityData();
              break;
            case 2:
              LoadAddressData();
              break;
            case 3:
              LoadCertificates();
              break;
            case 4:
              LoadPINs();
              break;
            case 5:
              txtNotes.Text = LoadNotes();
              break;
            default:
              // Should never occur, so don't do anyhing
              break;
          }
        }
        catch (EIDPTException ex)
        {
          switch (ex.ErrorCode)
          {
            case ResultCode.SC_ERROR_KEYPAD_CANCELLED:
              break;
            case ResultCode.SC_ERROR_PIN_CODE_INCORRECT:
              ShowErrorMessage("Pin Inválido", "O pin introduzido é incorrecto.");
              break;
            default:
              ShowErrorMessage("Erro ao lêr dados", "Ocorreu um erro ao lêr os dados do cartão." + ex.Message);
              break;
          }
        }
        finally
        {
          Cursor = Cursors.Default;
        }
        SetStatusText(string.Empty);
      }
    }

    public frmMain()
    {
      InitializeComponent();
    }

    private string GetCertificateSubjectName(string subject)
    {
      Match subjectMatch = Regex.Match(subject, REGEX_SUBJECT_NAME);
      if (subjectMatch.Success)
        return subjectMatch.Groups[1].Value;
      return string.Empty;
    }

    private string GetCertificateIssuerName(string issuer)
    {
      Match subjectMatch = Regex.Match(issuer, REGEX_ISSUER_NAME);
      if (subjectMatch.Success)
      {
        if (subjectMatch.Groups[1].Success)
          return subjectMatch.Groups[1].Value;
        return subjectMatch.Groups[2].Value;
      }
      return string.Empty;
    }

    private void LoadIdentityData()
    {
      Id citizen = EIDPT.GetID();
      Picture picture = EIDPT.GetPicture();

      System.IO.MemoryStream ms = new System.IO.MemoryStream(picture.Bytes, 0, picture.BytesLength, false);
      // JPEG2000 Support provided by CSJ2K (http://csj2k.codeplex.com/)
      Image tempImage = CSJ2K.J2kImage.FromStream(ms);
      ms.Close();
      pbPhoto.Image = tempImage;
      // Citizen Identity Data
      txtName.Text = citizen.Name;
      txtFirstName.Text = citizen.FirstName;
      txtSex.Text = citizen.Sex;
      txtHeight.Text = citizen.Height;
      txtNationality.Text = citizen.Nationality;
      txtBirthDate.Text = citizen.BirthDate;
      txtDocumentNumber.Text = citizen.CardNumber;
      txtExpiryDate.Text = citizen.ExpiryDate;
      txtCountry.Text = citizen.Country;
      txtParents.Text = string.Format("{0} {1}\r\n{2} {3}", citizen.FirstNameFather, citizen.NameFather, citizen.FirstNameMother, citizen.NameMother);
      txtCitizenNote.Text = citizen.Notes;

      // Other Card Data
      txtNIF.Text = citizen.NIF;
      txtNSS.Text = citizen.NSS;
      txtSNS.Text = citizen.SNS;
      txtCardVersion.Text = citizen.CardVersion;
      txtIssuingDate.Text = citizen.DeliveryDate;
      txtIssuingEntity.Text = citizen.DeliveryEntity;
      txtDocumentType.Text = citizen.DocumentType;
      txtRequestPlace.Text = citizen.Locale;
      if (EIDPT.IsActivated() == CardActivationStatus.ACTIVE)
        lnkCardActivation.Text = "O Cartão de Cidadão encontra-se activo.";
      else
        lnkCardActivation.Text = "O Cartão de Cidadão não se encontra activo.";
    }

    private void LoadAddressData()
    {
      Address address = EIDPT.GetAddress();
      txtDistrict.Text = address.DistrictDescription;
      txtMunicipality.Text = address.MunicipalityDescription;
      txtFreguesia.Text = address.FreguesiaDescription;
      txtAbrStreetType.Text = address.StreetTypeAbbreviation;
      txtStreetType.Text = address.StreetType;
      txtStreet.Text = address.Street;
      txtAbrBuilding.Text = address.BuildingAbbreviation;
      txtBuilding.Text = address.Building;
      txtDoor.Text = address.Door;
      txtFloor.Text = address.Floor;
			txtSide.Text = address.Side;
      txtRegion.Text = address.Place;
      txtLocality.Text = address.Locality;
      txtZIP4.Text = address.CP4;
      txtZIP3.Text = address.CP3;
      txtPostalLocality.Text = address.Postal;
    }

    private void LoadCertificates()
    {
      Certificate[] certificates = EIDPT.GetCertificates();
      tvCertificates.Nodes.Clear();// .Items.Clear();
      foreach (Certificate certificate in certificates)
      {
        X509Certificate xCertificate = new X509Certificate(certificate.Bytes);
        TreeNode newNode = new TreeNode();
        newNode.Text = this.GetCertificateSubjectName(xCertificate.Subject);
        newNode.Tag = xCertificate;
        tvCertificates.Nodes.Add(newNode);
      }
    }

    private void LoadPINs()
    {
      Pin[] pins = EIDPT.GetPINs();
      tvPINs.Nodes.Clear();
      foreach (Pin pin in pins)
      {
        TreeNode newNode = new TreeNode(pin.Label);
        newNode.Tag = pin;
        tvPINs.Nodes.Add(newNode);
      }
    }

    private string LoadNotes()
    {
			byte[] fileBytes = EIDPT.ReadFile(notesFilePath, (byte)PinId.AUTHORIZATION_PIN_ID);
      return System.Text.Encoding.UTF8.GetString(fileBytes);
    }

    private void SaveNotes(string notes)
    {
      Byte[] fileBytes = System.Text.Encoding.UTF8.GetBytes(notes);
      EIDPT.WriteFile(notesFilePath, fileBytes, (byte)PinId.AUTHORIZATION_PIN_ID);
    }

    void ChangeAddressHandler(AddressChangeState state)
    {
      ShowInfoMessage("Estado da alteração da morada", "Estado actual: " + state.ToString());
    }

    void ChangePinHandler(CapPinChangeState state)
    {
      ShowInfoMessage("Estado da alteração do PIN", "Estado actual: " + state.ToString());
    }

    private void frmMain_Load(object sender, EventArgs e)
    {			
      proxyInfo = new ProxyInfo();
      scWatcher = SCWatcher.GetInstance(); // Daemon starts when instantiated
      this.currentReader = string.Empty;
      this.readerList = new List<string>(scWatcher.ListReaders());
      if (this.readerList.Count > 0)
        this.currentReader = this.readerList[0];
      scWatcher.CardInserted += new SCWatcher.CardInsertedHandler(scWatcher_CardInserted);
      scWatcher.CardRemoved += new SCWatcher.CardRemovedHandler(scWatcher_CardRemoved);
      scWatcher.ReaderInserted += new SCWatcher.ReaderInsertedHandler(scWatcher_ReaderInserted);
      scWatcher.ReaderRemoved += new SCWatcher.ReaderRemovedHandler(scWatcher_ReaderRemoved);
      scWatcher.OnError += new SCWatcher.ErrorHandler(scWatcher_OnError);
      // TODO: Test and validate the use of delegate as function pointer
      EIDPT eId = EIDPT.GetInstance();
      eId.ChangeAddressHandler += new EIDPT.ChangeAddressCallback(ChangeAddressHandler);
      eId.ChangePinHandler += new EIDPT.CapPinChangeCallback(ChangePinHandler);
    }

    void scWatcher_CardInserted(string readerName, string cardName)
    {
      ShowInfoMessage("Cartão inserido", "Um cartão foi inserido.\r\nNome (ATR) = " + cardName);
      if (readerName != this.currentReader || this.isCardPresent || !this.knownCards.Contains(cardName))
        return;

      ShowInfoMessage("Cartão de Cidadão", "Foi detectado um cartão de cidadão.");

      try
      {
        SetStatusText("A inicializar o acesso ao cartão...");
        EIDPT.Init(readerName);
        EIDPT.SetSODChecking(false);
      }
      catch (EIDPTException ex)
      {
        SetStatusText(string.Empty);
        EIDPT.Exit(ExitMode.LEAVE_CARD);
        ShowErrorMessage("Erro a inicializar o acesso ao cartão", ex.Message);
        return;
      }
      
      SetStatusText(string.Empty);
      this.currentReader = readerName;
      this.isCardPresent = true;
      LoadData(); // Should resume presentation on UI Thread
    }

    void scWatcher_CardRemoved(string readerName)
    {
      ShowInfoMessage("Cartão removido", "Um cartão foi removido.\r\nNome do leitor: " + readerName);
      if (!this.isCardPresent)
        return;
      if (this.currentReader != readerName)
        return;

      ShowInfoMessage("Cartão de Cidadão", "O cartão de cidadão foi removido.");

      EIDPT.Exit(ExitMode.LEAVE_CARD);
      this.isCardPresent = false;
    }

    void scWatcher_ReaderInserted(string readerName)
    {
      ShowInfoMessage("Leitor de cartões", "Um leitor foi ligado.\r\nNome do leitor: " + readerName);
      if (!readerList.Contains(readerName))
        this.readerList.Add(readerName);

      if (this.currentReader.Length == 0)
        this.currentReader = readerName;
    }

    void scWatcher_ReaderRemoved(string readerName)
    {
      ShowInfoMessage("Leitor de cartões", "Um leitor foi desligado.\r\nNome do leitor: " + readerName);
      if (readerList.Contains(readerName))
        readerList.Remove(readerName);

      if (this.currentReader != readerName)
        return;

      EIDPT.Exit(ExitMode.LEAVE_CARD);
      this.isCardPresent = false;
      this.currentReader = string.Empty;
    }

    void scWatcher_OnError(int errorCode, string errorMessage)
    {
      ShowErrorMessage("Erro ao monitorizar leitores e cartões", errorMessage);
    }

    private void frmMain_FormClosing(object sender, FormClosingEventArgs e)
    {
      SetStatusText("A terminar a monitorização de leitores e cartões...");
      this.scWatcher.Stop();
      if (this.isCardPresent)
      {
        EIDPT.Exit(ExitMode.LEAVE_CARD);
      }
    }

    private void tabControl1_Selecting(object sender, TabControlCancelEventArgs e)
    {
      LoadData();
    }

    private void readDataMenuItem_Click(object sender, EventArgs e)
    {
      LoadData();
    }

    private void exitMenuItem_Click(object sender, EventArgs e)
    {
      this.Close();
    }

    private void settingsMenuItem_Click(object sender, EventArgs e)
    {
      frmSettings settingsForm = new frmSettings(this.readerList, this.currentReader, this.proxyInfo);
      settingsForm.UpdateSettings += new frmSettings.UpdateSettingsHandler(settingsForm_UpdateSettings);
      settingsForm.ShowDialog(this);
    }

    void settingsForm_UpdateSettings(object sender, UpdateSettingsEventArgs e)
    {
      this.currentReader = e.SelectedReader;
      this.proxyInfo = e.ProxyConfiguration;
    }

    private void aboutMenuItem_Click(object sender, EventArgs e)
    {
      frmAbout aboutForm = new frmAbout();
      aboutForm.ShowDialog(this);
    }

    private void tvCertificates_NodeMouseClick(object sender, TreeNodeMouseClickEventArgs e)
    {
      X509Certificate xCertificate = (X509Certificate)e.Node.Tag;
      txtCertificateSubject.Text = this.GetCertificateSubjectName(xCertificate.Subject);
      txtCertificateIssuer.Text = this.GetCertificateIssuerName(xCertificate.Issuer);
      txtCertificateDate.Text = xCertificate.GetEffectiveDateString();
      txtCertificateExpirationDate.Text = xCertificate.GetExpirationDateString();
      X509Certificate2 xCertificate2 = new X509Certificate2(xCertificate.GetRawCertData());
      RSA rsa = (RSACryptoServiceProvider)xCertificate2.PublicKey.Key;
      txtCertificateKeySize.Text = rsa.KeySize + " bits";
      txtCertificateSerialNumber.Text = xCertificate2.SerialNumber;

    }

    private void btnShowCertificate_Click(object sender, EventArgs e)
    {
      if (tvCertificates.SelectedNode == null)
        return;
      X509Certificate xCertificate = (X509Certificate)tvCertificates.SelectedNode.Tag;
      X509Certificate2 xCertificate2 = new X509Certificate2(xCertificate.GetRawCertData());
      System.Security.Cryptography.X509Certificates.X509Certificate2UI.DisplayCertificate(xCertificate2);
    }

    private void btnValidateCertificate_Click(object sender, EventArgs e)
    {
      if (tvCertificates.SelectedNode == null)
        return;
      X509Certificate xCertificate = (X509Certificate)tvCertificates.SelectedNode.Tag;
      X509Certificate2 xCertificate2 = new X509Certificate2(xCertificate.GetRawCertData());
      X509Chain chain = new X509Chain();
      chain.ChainPolicy.RevocationFlag = X509RevocationFlag.ExcludeRoot;
      chain.ChainPolicy.RevocationMode = X509RevocationMode.Online;
      chain.ChainPolicy.UrlRetrievalTimeout = new TimeSpan(0, 0, 60);
      chain.ChainPolicy.VerificationFlags = X509VerificationFlags.NoFlag;
      SetStatusText("A validar o certificado...");
      try
      {
        if (chain.Build(xCertificate2))
          MessageBox.Show(this, "O certificado encontra-se válido.", "Informação", MessageBoxButtons.OK, MessageBoxIcon.Information);
        else
          MessageBox.Show(this, "O certificado não é válido.", "Informação", MessageBoxButtons.OK, MessageBoxIcon.Warning);
      }
      catch (Exception ex)
      {
        MessageBox.Show(this, "Ocorreu um erro ao validar o certificado.\r\nDetalhes: " + ex.Message, "Erro",  MessageBoxButtons.OK, MessageBoxIcon.Error);
      }
      SetStatusText(string.Empty);
    }

    private void tvPINs_AfterSelect(object sender, TreeViewEventArgs e)
    {
      Pin pin = (Pin)e.Node.Tag;
      txtPINType.Text = pin.Type.ToString();
      txtPINId.Text = pin.Id.ToString();
      txtPINState.Text = "Resta(m) " + pin.TriesLeft + " tentativa(s)";
    }

    private void btnValidatePIN_Click(object sender, EventArgs e)
    {
      if (tvPINs.SelectedNode == null)
        return;
      Pin pin = (Pin)tvPINs.SelectedNode.Tag;
      SetStatusText("A validar o PIN...");
      try
      {
        pin.TriesLeft = EIDPT.VerifyPINNoAlert(pin.Id, null);
        txtPINState.Text = "Resta(m) " + pin.TriesLeft + " tentativa(s)";
        tvPINs.SelectedNode.Tag = pin;
      }
      catch (EIDPTException ex)
      {
        switch (ex.ErrorCode)
        {
          case ResultCode.SC_ERROR_KEYPAD_CANCELLED:
            break;
          case ResultCode.SC_ERROR_PIN_CODE_INCORRECT:
            MessageBox.Show(this, "O pin introduzido é incorrecto. Já só tem " + pin.TriesLeft + " tentativa(s).", "Pin Inválido", MessageBoxButtons.OK, MessageBoxIcon.Warning);
            break;
          default:
            MessageBox.Show(this, ex.Message, "Erro ao validar o PIN", MessageBoxButtons.OK, MessageBoxIcon.Error);
            break;
        }
      }
      SetStatusText(string.Empty);
    }

    private void btnChangePIN_Click(object sender, EventArgs e)
    {
      if (tvPINs.SelectedNode == null)
        return;
      Pin pin = (Pin)tvPINs.SelectedNode.Tag;
      SetStatusText("A solicitar alteração do PIN...");
      try
      {
        //EIDPT.UnblockPIN_Ext(pin.Id, "123", "1234", (uint)PinUnblockType.NEW_PIN);
        pin.TriesLeft = EIDPT.ChangePIN(pin.Id, null, null);
        txtPINState.Text = "Resta(m) " + pin.TriesLeft + " tentativa(s)";
        tvPINs.SelectedNode.Tag = pin;
      }
      catch (EIDPTException ex)
      {
        switch (ex.ErrorCode)
        {
          case ResultCode.SC_ERROR_KEYPAD_CANCELLED:
            break;
          case ResultCode.SC_ERROR_PIN_CODE_INCORRECT:
            MessageBox.Show(this, "O pin actual introduzido é incorrecto. Já só tem " + pin.TriesLeft + " tentativa(s).", "Pin Inválido", MessageBoxButtons.OK, MessageBoxIcon.Warning);
            break;
          default:
            MessageBox.Show(this, ex.Message, "Erro ao alterar o PIN", MessageBoxButtons.OK, MessageBoxIcon.Error);
            break;
        }
      }
      SetStatusText(string.Empty);
    }

    private void btnSaveNotes_Click(object sender, EventArgs e)
    {
      if (!isCardPresent)
      {
        MessageBox.Show("Não foi detectado nenhum cartão de cidadão.", "Informação", MessageBoxButtons.OK, MessageBoxIcon.Information);
        return;
      }
      SetStatusText("A guardar dados no cartão...");
      try
      {
        SaveNotes(txtNotes.Text);
      }
      catch (EIDPTException ex)
      {
        MessageBox.Show("Ocorreu um erro ao guardar os dados." + ex.Message, "Erro ao gravar dados", MessageBoxButtons.OK, MessageBoxIcon.Error);
      }
      SetStatusText(string.Empty);
    }

    private void txtNotes_TextChanged(object sender, EventArgs e)
    {
      Byte[] fileBytes = System.Text.Encoding.UTF8.GetBytes(txtNotes.Text);
      txtNotesRemainingBytes.Text = (1000 - fileBytes.Length / sizeof(uint)).ToString();
    }
  }
}
